import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:http/http.dart' as http;
import 'package:lib_bitcoin_base/bitcoin_base.dart';
import 'package:lib_database/lib_database.dart';
import 'package:lib_util/lib_util.dart';

import '../../kai_print.dart';

class RpcClient {
  RpcClient._privateConstructor();

  static final RpcClient _instance = RpcClient._privateConstructor();

  factory RpcClient() {
    return _instance;
  }

  _formatBalance(String amount, {int decimals = 18, int fractionDigits = Config.balanceFixedNum}) {
    return Utils.formatStringBalance(
        (BigInt.parse(Utils.ensureRemoveHexPrefix(amount), radix: 16) / BigInt.from(10).pow(decimals)).toString(),
        fractionDigits: fractionDigits);
  }

  // 检查 跨链 信息
  Future<CrossInfoBean> checkCrossInfo(Token sellToken, Token buyToken) async {
    Chain sellChain = sellToken.getChain()!;
    Chain buyChain = buyToken.getChain()!;

    String sellRpc = ObjectBox().nodeInfoModel.getSelectedNodeInfo(sellChain.shortName).url;
    String buyRpc = ObjectBox().nodeInfoModel.getSelectedNodeInfo(buyChain.shortName).url;

    String sellAddress = sellToken.getWalletInfo()!.address;
    String buyAddress = buyToken.getWalletInfo()!.address;

    if (sellChain == ChainList.tronChain) {
      sellRpc = "$sellRpc${sellRpc.endsWith('/') ? '' : '/'}jsonrpc";
      sellAddress = "0x${Utils.convertTronAddressFromBase58ToHex(sellAddress)}";
    }

    if (buyChain == ChainList.tronChain) {
      buyRpc = "$buyRpc${buyRpc.endsWith('/') ? '' : '/'}jsonrpc";
      buyAddress = "0x${Utils.convertTronAddressFromBase58ToHex(buyAddress)}";
    }

    Future<String> sellTokenBalanceFuture = rpcCallResult(
        rpc: sellRpc,
        from: sellAddress,
        to: sellChain == ChainList.tronChain
            ? "0x${Utils.convertTronAddressFromBase58ToHex(sellToken.contract)}"
            : sellToken.contract,
        data: "0x70a08231000000000000000000000000${sellAddress.toLowerCase().substring(2)}");

    Future<String> buyTokenBalanceFuture = rpcCallResult(
        rpc: buyRpc,
        from: buyAddress,
        to: buyChain == ChainList.tronChain
            ? "0x${Utils.convertTronAddressFromBase58ToHex(buyToken.contract)}"
            : buyToken.contract,
        data: "0x70a08231000000000000000000000000${buyAddress.toLowerCase().substring(2)}");

    Future<String> maxAmountFuture;

    if (sellChain == ChainList.ompChain && buyChain == ChainList.bscChain) {
      maxAmountFuture = rpcCallResult(
          rpc: buyRpc,
          from: sellAddress,
          to: buyToken.contract,
          data: "0x70a08231000000000000000000000000${Config.anySwapContracts[buyChain]!.toLowerCase().substring(2)}");
    } else if (sellChain == ChainList.ompChain && buyChain == ChainList.tronChain) {
      maxAmountFuture = rpcCallResult(
          rpc: buyRpc,
          from: sellAddress,
          to: "0x${Utils.convertTronAddressFromBase58ToHex(buyToken.contract)}",
          data:
              "0x70a08231000000000000000000000000${Utils.convertTronAddressFromBase58ToHex(Config.ompBridgeContracts[buyChain]!)}");
    } else {
      maxAmountFuture = Future.value("");
    }

    Future<String>? bridgeInfo;

    //omp <==> bsc ,走AnySwap
    if ((sellChain == ChainList.ompChain && buyChain == ChainList.bscChain) ||
        (sellChain == ChainList.bscChain && buyChain == ChainList.ompChain)) {
      Future<String> minAmountFuture = rpcCallResult(
          rpc: sellRpc,
          from: sellAddress,
          to: Config.anySwapDataContracts[sellChain]!,
          data: "0x1d87c47f"
              "000000000000000000000000000000000000000000000000000000000000002"
              "000000000000000000000000000000000000000000000000000000000000000"
              "096d696e416d6f756e740000000000000000000000000000000000000000000000");

      Future<String> feeTypeFuture = rpcCallResult(
          rpc: sellRpc,
          from: sellAddress,
          to: Config.anySwapDataContracts[sellChain]!,
          data:
              "0x1d87c47f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c63726f7373466565547970650000000000000000000000000000000000000000");

      Future<String> feeFuture = rpcCallResult(
          rpc: sellRpc,
          from: sellAddress,
          to: Config.anySwapDataContracts[sellChain]!,
          data: "0x1d87c47f0000000000000000000000000000000000000000000000000000000000000020"
              "000000000000000000000000000000000000000000000000000000000000000863726f7373466565"
              "000000000000000000000000000000000000000000000000");

      List<String> resultList = await Future.wait(
          [sellTokenBalanceFuture, buyTokenBalanceFuture, minAmountFuture, feeTypeFuture, feeFuture, maxAmountFuture]);
      String sellTokenBalance = resultList[0];
      String buyTokenBalance = resultList[1];
      String minAmount = resultList[2];
      String feeType = resultList[3];
      String fee = resultList[4];

      String maxAmount = resultList[5];

      int formatFeeType = BigInt.parse(Utils.ensureRemoveHexPrefix(feeType), radix: 16).toInt();
      String formatFee = _formatBalance(fee, decimals: 0 == formatFeeType ? 6 : 18, fractionDigits: 0);
      String feeDesc = 0 == formatFeeType ? '$formatFee%' : '$formatFee USDT';

      return CrossInfoBean(
          minAmount: _formatBalance(minAmount, fractionDigits: 0) + ' USDT',
          maxAmount: Utils.isEmpty(maxAmount) ? '' : _formatBalance(maxAmount, fractionDigits: 0) + ' USDT',
          feeDesc: feeDesc,
          balanceBuy: _formatBalance(buyTokenBalance),
          balanceSell: _formatBalance(sellTokenBalance));
    } else if (sellChain == ChainList.ompChain && buyChain == ChainList.tronChain) {
      //omp ==> tron ,走ompBridge
      bridgeInfo = rpcCallResult(
          rpc: sellRpc,
          from: sellAddress,
          to: Config.ompBridgeContracts[sellChain]!,
          data:
              "0xcbc78798000000000000000000000000437635ba437728c577f4234f3a3f48619f02d75c000000000000000000000000000000000000000000000000000000002b6653dc000000000000000000000000a614f803b6fd780986a42c78ec9c7f77e6ded13c");
    } else if (sellChain == ChainList.tronChain && buyChain == ChainList.ompChain) {
      //tron ==> omp ,走ompBridge
      bridgeInfo = rpcCallResult(
          rpc: sellRpc,
          from: sellAddress,
          to: "0x${Utils.convertTronAddressFromBase58ToHex(Config.ompBridgeContracts[sellChain]!)}",
          data:
              "0xcbc78798000000000000000000000000a614f803b6fd780986a42c78ec9c7f77e6ded13c0000000000000000000000000000000000000000000000000000000000002d20000000000000000000000000437635ba437728c577f4234f3a3f48619f02d75c");
    } else {
      throw 'no support';
    }

    List<String> resultList =
        await Future.wait([sellTokenBalanceFuture, buyTokenBalanceFuture, bridgeInfo, maxAmountFuture]);

    String balanceSell = _formatBalance(resultList[0], decimals: sellChain == ChainList.tronChain ? 6 : 18);
    String balanceBuy = _formatBalance(resultList[1], decimals: buyChain == ChainList.tronChain ? 6 : 18);

    String fee = resultList[2].substring(66, 130);
    String feeType = resultList[2].substring(130, 194);
    String minAmount = resultList[2].substring(194, 258);

    String maxAmount = resultList[3];

    int formatFeeType = BigInt.parse(Utils.ensureRemoveHexPrefix(feeType), radix: 16).toInt();
    String formatFee = _formatBalance(fee,
        decimals: 0 == formatFeeType ? 2 : (sellChain == ChainList.tronChain ? 6 : 18), fractionDigits: 0);
    String feeDesc = 0 == formatFeeType ? '$formatFee%' : '$formatFee USDT';

    return CrossInfoBean(
        minAmount:
            _formatBalance(minAmount, decimals: sellChain == ChainList.tronChain ? 6 : 18, fractionDigits: 0) + ' USDT',
        maxAmount: Utils.isEmpty(maxAmount) ? '' : _formatBalance(maxAmount, fractionDigits: 0, decimals: 6) + ' USDT',
        feeDesc: feeDesc,
        balanceBuy: balanceBuy,
        balanceSell: balanceSell);
  }

  Future<bool> checkBlockApiValid(String domain) async {
    http.Response resp = await http
        .get(Uri.parse('$domain/api?module=account&action=balance&address=0x70F657164e5b75689b64B7fd1fA275F334f28e18'));
    return resp.statusCode >= 200 && resp.statusCode < 300;
  }

  Future<Map<String, dynamic>> createRpcRequest({required String rpc, required Map<String, dynamic> body}) async {
    http.Response resp = await http.post(Uri.parse(rpc),
        headers: {HttpHeaders.contentTypeHeader: 'application/json', HttpHeaders.acceptHeader: "application/json"},
        body: json.encode(body));
    if (resp.statusCode == 308) {
      //post 重定向
      resp = await http.post(Uri.parse(resp.headers["location"]!),
          headers: {HttpHeaders.contentTypeHeader: 'application/json', HttpHeaders.acceptHeader: "application/json"},
          body: json.encode(body));
    }
    String result = utf8.decode(resp.bodyBytes);
    // Config.logger.d('rpc request:\n${json.encode(body)}\nresponse:\n$result');
    // Config.logger.d('rpc:$rpc\nrpc request:\n${json.encode(body)}\nresponse:\n$result');
    Map<String, dynamic> map = json.decode(result);
    return map;
  }

  Future<String> ethCall({required String rpc, required String from, required String to, required String data}) async {
    String result = json.encode(await rpcCall(rpc: rpc, from: from, to: to, data: data));
    return result;
  }

  Future<Map<String, dynamic>> rpcCall(
      {required String rpc, required String from, required String to, required String data}) async {
    return createRpcRequest(rpc: rpc, body: {
      "jsonrpc": "2.0",
      "method": "eth_call",
      "params": [
        {
          "from": from,
          "to": to,
          "data": data,
        },
        "latest"
      ],
      "id": 1
    });
  }

  Future<String> rpcCallResult(
      {required String rpc, required String from, required String to, required String data}) async {
    return (await rpcCall(rpc: rpc, from: from, to: to, data: data))['result'];
  }

  // 获取 币种余额
  Future<String> getTokenBalance(
      {required String rpc,
      required String contract,
      required String wallet,
      int chainId = 1,
      int decimals = 18}) async {
    Map<String, dynamic> map = await createRpcRequest(rpc: rpc, body: {
      "jsonrpc": "2.0",
      "method": "eth_call",
      "params": [
        {
          "from": "0x0000000000000000000000000000000000000000",
          "to": contract,
          "data": "0x70a08231000000000000000000000000${wallet.toLowerCase().substring(2)}",
        },
        "latest"
      ],
      "id": chainId
    });

    Log().d('message $map');
    BigInt balance = BigInt.parse(Utils.ensureRemoveHexPrefix(map['result']), radix: 16);

    return Utils.formatStringBalance((balance / BigInt.from(10).pow(decimals)).toString(),
        fractionDigits: Config.balanceFixedNum);
  }

  //  返回当前 chainId
  Future<int> chainId({required String rpc}) async {
    Map<String, dynamic> map =
        await createRpcRequest(rpc: rpc, body: {"jsonrpc": "2.0", "method": "eth_chainId", "id": 0});

    String chainId = map['result'];
    if (chainId.startsWith('0x')) {
      chainId = chainId.substring(2);
    }
    return int.parse(chainId, radix: 16);
  }

  // 获取 当前 coinbase

  // 获取
  Future<String> ethGetBalance({required WalletInfo wallet}) async {
    String walletAddress = wallet.address;
    Chain chain = wallet.getChain()!;
    String rpc = ObjectBox().nodeInfoModel.getSelectedNodeInfo(chain.shortName).url;
    if (chain.type == ChainType.tron) {
      rpc = "$rpc${rpc.endsWith('/') ? '' : '/'}jsonrpc";
      walletAddress = "0x${Utils.convertTronAddressFromBase58ToHex(wallet.address)}";
    }
    Map<String, dynamic> params = {
      "jsonrpc": "2.0",
      "method": "eth_getBalance",
      "params": [walletAddress, "latest"],
      "id": 1
    };
    Map<String, dynamic> map = await createRpcRequest(rpc: rpc, body: params);
    BigInt balance = BigInt.parse(Utils.ensureRemoveHexPrefix(map['result']), radix: 16);
    return Utils.formatStringBalance((balance / BigInt.from(10).pow(18)).toString(),
        fractionDigits: Config.balanceFixedNum);
  }

  Future<TransactionDetails> ethTransactionDetails({required String rpc, required String txHash}) async {
    Map<String, dynamic> result = await createRpcRequest(rpc: rpc, body: {
      "jsonrpc": "2.0",
      "method": "eth_getTransactionReceipt",
      "params": [txHash],
      "id": 1
    });
    Map<String, dynamic>? map = result['result'];
    if (map == null) {
      return TransactionDetails(gasFee: '0', block: '0', confirmed: false);
    }
    BigInt gas = BigInt.parse(Utils.ensureRemoveHexPrefix(map['gasUsed']), radix: 16);
    BigInt gasPrice = BigInt.parse(Utils.ensureRemoveHexPrefix(map['effectiveGasPrice']), radix: 16);
    String gasFee = Utils.formatStringBalance((gas * gasPrice / BigInt.from(10).pow(18)).toString(),
        fractionDigits: Config.gasFeeFixedNum);
    int? status =
        map['status'] != null ? (BigInt.parse(Utils.ensureRemoveHexPrefix(map['status']), radix: 16).toInt()) : 2;

    String block = BigInt.parse(Utils.ensureRemoveHexPrefix(map['blockNumber']), radix: 16).toString();
    return TransactionDetails(
        gasFee: gasFee,
        status: status,
        block: block,
        confirmed: (Utils.isNotEmpty(gasFee) && Utils.isNotEmpty(block) && (int.parse(block) != 0)));
  }

  Future<TransactionDetails> ethTransactionDetails2({required String rpc, required String txHash}) async {
    //  获取 具体交易信息 通过hash
    Map<String, dynamic> map = await createRpcRequest(rpc: rpc, body: {
      "jsonrpc": "2.0",
      "method": "eth_getTransactionByHash",
      "params": [txHash],
      "id": 1
    });
    BigInt gas = BigInt.parse(Utils.ensureRemoveHexPrefix(map['result']['gas']), radix: 16);
    BigInt gasPrice = BigInt.parse(Utils.ensureRemoveHexPrefix(map['result']['gasPrice']), radix: 16);
    String gasFee = Utils.formatStringBalance((gas * gasPrice / BigInt.from(10).pow(18)).toString(),
        fractionDigits: Config.balanceFixedNum);
    String chainId = BigInt.parse(Utils.ensureRemoveHexPrefix(map['result']['chainId']), radix: 16).toInt().toString();
    String? timeStamp = null;
    String? value = Utils.formatStringBalance(
            (BigInt.parse(Utils.ensureRemoveHexPrefix(map['result']['value']), radix: 16) / BigInt.from(10).pow(18))
                .toString(),
            fractionDigits: Config.balanceFixedNum)
        .toString();
    if (map['result']['blockHash'] != null) {
      Map<String, dynamic> map1 = await createRpcRequest(rpc: rpc, body: {
        "jsonrpc": "2.0",
        "method": "eth_getBlockByHash",
        "params": [map['result']['blockHash'], true],
        "id": 1
      });
      // 获取 区块时间
      timeStamp = BigInt.parse(Utils.ensureRemoveHexPrefix(map1['result']['timestamp']), radix: 16).toInt().toString();
    }
    print('map.blockHash  ${map['result']['blockHash']}');
    Map<String, dynamic> result = await createRpcRequest(rpc: rpc, body: {
      "jsonrpc": "2.0",
      "method": "eth_getTransactionReceipt",
      "params": [txHash],
      "id": 1
    });
    Map<String, dynamic>? map2 = result['result'];

    // 获取 区块状态，以及具体 状态
    String? contractAddress = null;
    int? status = null;
    String? actualGas = null;
    if (map2 != null) {
      contractAddress = map2['contractAddress'] ?? null;
      status = map2['status'] != null
          ? (BigInt.parse(Utils.ensureRemoveHexPrefix(map2['status']), radix: 16).toInt())
          : null;
      actualGas = Utils.formatStringBalance(
              (BigInt.parse(Utils.ensureRemoveHexPrefix(map2['gasUsed']), radix: 16) / BigInt.from(10).pow(18))
                  .toString(),
              fractionDigits: Config.balanceFixedNum)
          .toString();
    }
    return TransactionDetails(
        value: value,
        actualGas: actualGas,
        contractAddress: contractAddress,
        status: status,
        time: timeStamp,
        hash: map['result']['hash'],
        chainId: chainId,
        formAddress: map['result']['from'],
        toAddress: map['result']['to'],
        gasFee: gasFee,
        block: BigInt.parse(Utils.ensureRemoveHexPrefix(map['result']['blockNumber']), radix: 16).toString());
  }

  Future<bool> isContract({required Chain chain, required String address}) async {
    String rpc = ObjectBox().nodeInfoModel.getSelectedNodeInfo(chain.shortName).url;
    if (chain.type == ChainType.tron) {
      rpc = "$rpc${rpc.endsWith('/') ? '' : '/'}jsonrpc";
      address = "0x${Utils.convertTronAddressFromBase58ToHex(address)}";
    }
    Map<String, dynamic> requestData = {
      "jsonrpc": "2.0",
      "method": "eth_getCode",
      "params": [address, "latest"],
      "id": 1
    };
    Map<String, dynamic> map = await createRpcRequest(rpc: rpc, body: requestData);
    String result = map['result'];
    return result.length > 2;
  }

  Future<TransactionDetails> tronTransactionDetails({required String txHash}) async {
    dynamic resp = await http.get(Uri.https('apilist.tronscanapi.com', '/api/transaction-info', {'hash': txHash}),
        headers: {'Content-Type': 'application/json'});
    String result = utf8.decode(resp.bodyBytes);
    Config.logger.d('tron details:$result');
    Map<String, dynamic> map = json.decode(result);
    Map<String, dynamic> cost = map['cost'];
    String gasFee = Utils.formatStringBalance(
        (BigInt.from(cost['energy_fee']! as int) / BigInt.from(10).pow(6)).toString(),
        fractionDigits: 4);
    return TransactionDetails(gasFee: gasFee, block: map['block'].toString());
  }

  Future<TransactionDetails> btcTransactionDetails({required String txId, required bool mainNet}) async {
    BitcoinTransactionDetails res = await SimpleBitcoin().getBitcoinTransactionInfo(txId, mainNet);

    String? gasFee = null == res.fee
        ? null
        : Utils.formatDoubleBalance(res.fee! / pow(10, SimpleBitcoin.decimals),
            fractionDigits: SimpleBitcoin.decimals, trimDecimalZero: true);
    return TransactionDetails(
        gasFee: gasFee ?? '', block: null == res.block ? '' : res.block!.toString(), confirmed: res.confirmed);
  }
}

class TransactionDetails {
  String gasFee;
  String? actualGas;
  String block;
  String? chainId;
  String? hash;
  int? status;
  String? value;
  String? contractAddress;
  String? formAddress;
  String? toAddress;
  String? time;
  bool? confirmed;

  TransactionDetails(
      {required this.gasFee,
      required this.block,
      this.actualGas,
      this.time,
      this.value,
      this.confirmed,
      this.contractAddress,
      this.formAddress,
      this.chainId,
      this.status,
      this.hash,
      this.toAddress});
}
